function replaceDocument(docString) {
    var doc = document.open("text/html");
  
    doc.write(docString);
    doc.close();
  }
  
  function doAjaxSubmit(e) {
    var form = $(this);
    var btn = $(this.clk);
    var method = (
      btn.data('method') ||
      form.data('method') ||
      form.attr('method') || 'GET'
    ).toUpperCase();
  
    if (method === 'GET') {
      // GET requests can always use standard form submits.
      return;
    }
  
    var contentType =
      form.find('input[data-override="content-type"]').val() ||
      form.find('select[data-override="content-type"] option:selected').text();
  
    if (method === 'POST' && !contentType) {
      // POST requests can use standard form submits, unless we have
      // overridden the content type.
      return;
    }
  
    // At this point we need to make an AJAX form submission.
    e.preventDefault();
  
    var url = form.attr('action');
    var data;
  
    if (contentType) {
      data = form.find('[data-override="content"]').val() || ''
  
      if (contentType === 'multipart/form-data') {
        // We need to add a boundary parameter to the header
        // We assume the first valid-looking boundary line in the body is correct
        // regex is from RFC 2046 appendix A
        var boundaryCharNoSpace = "0-9A-Z'()+_,-./:=?";
        var boundaryChar = boundaryCharNoSpace + ' ';
        var re = new RegExp('^--([' + boundaryChar + ']{0,69}[' + boundaryCharNoSpace + '])[\\s]*?$', 'im');
        var boundary = data.match(re);
        if (boundary !== null) {
          contentType += '; boundary="' + boundary[1] + '"';
        }
        // Fix textarea.value EOL normalisation (multipart/form-data should use CR+NL, not NL)
        data = data.replace(/\n/g, '\r\n');
      }
    } else {
      contentType = form.attr('enctype') || form.attr('encoding')
  
      if (contentType === 'multipart/form-data') {
        if (!window.FormData) {
          alert('Your browser does not support AJAX multipart form submissions');
          return;
        }
  
        // Use the FormData API and allow the content type to be set automatically,
        // so it includes the boundary string.
        // See https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects
        contentType = false;
        data = new FormData(form[0]);
      } else {
        contentType = 'application/x-www-form-urlencoded; charset=UTF-8'
        data = form.serialize();
      }
    }
  
    var ret = $.ajax({
      url: url,
      method: method,
      data: data,
      contentType: contentType,
      processData: false,
      headers: {
        'Accept': 'text/html; q=1.0, */*'
      },
    });
  
    ret.always(function(data, textStatus, jqXHR) {
      if (textStatus != 'success') {
        jqXHR = data;
      }
  
      var responseContentType = jqXHR.getResponseHeader("content-type") || "";
  
      if (responseContentType.toLowerCase().indexOf('text/html') === 0) {
        replaceDocument(jqXHR.responseText);
  
        try {
          // Modify the location and scroll to top, as if after page load.
          history.replaceState({}, '', url);
          scroll(0, 0);
        } catch (err) {
          // History API not supported, so redirect.
          window.location = url;
        }
      } else {
        // Not HTML content. We can't open this directly, so redirect.
        window.location = url;
      }
    });
  
    return ret;
  }
  
  function captureSubmittingElement(e) {
    var target = e.target;
    var form = this;
  
    form.clk = target;
  }
  
  $.fn.ajaxForm = function() {
    var options = {}
  
    return this
      .unbind('submit.form-plugin  click.form-plugin')
      .bind('submit.form-plugin', options, doAjaxSubmit)
      .bind('click.form-plugin', options, captureSubmittingElement);
  };